using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

using Horizon;

namespace HorizonTestClient
{
    public partial class MainForm : Form
    {
        ServerRequest req;

        Library lib; // Current library

        OptionsForm optForm = new OptionsForm();

        // Indicate that application is exiting
        bool exiting = false;
        
        /// <summary>
        /// Create a new MainForm.  This is the starting point responsible for
        /// reading saved settings and initializing things.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            
            optForm.Libraries = Deserialize("Libraries.ser");

            UpdateAllPatrons();

            req = new ServerRequest("search", "", "basic_search");

            // Manually add the most relevant columns to the grid
            // By default, a DataViewGrid will create columns for every bound property
            holdingsDataGridView.AutoGenerateColumns = false;
            AddHoldingsColumn("Author", "Author");
            AddHoldingsColumn("Title", "Title");
            AddHoldingsColumn("Format", "Format");
            AddHoldingsColumn("Published", "PubDate");
            AddHoldingsColumn("Checked Out", "CheckOutDate");
            AddHoldingsColumn("Due", "DueDate");

            // Convert hours to milliseconds
            updatePatronsTimer.Interval = (int)(Properties.Settings.Default.UpdateInterval * 60 * 60 * 1000);

            updatePatronsTimer.Enabled = Properties.Settings.Default.AutomaticUpdatesFlag;

            // Make sure any changes are reflected from settings window
            optForm.VisibleChanged += new EventHandler(optForm_VisibleChanged);
        }

        /// <summary>
        /// Updates details for all users for overdue alerts
        /// </summary>
        private void UpdateAllPatrons()
        {
            StringBuilder sb = new StringBuilder();
            string status = "";

            if (optForm.Libraries.Count > 0)
                appNotifyIcon.ShowBalloonTip(30, "Check it Out!", "Connecting to the library...", ToolTipIcon.Info);
            else
                appNotifyIcon.ShowBalloonTip(30, "Check it Out!", "Right-click, then click Configure to setup your library", ToolTipIcon.Info);

            // Login each user to find books due, fines levied
            foreach (Library lib in optForm.Libraries)
            {
                foreach (Patron user in lib.Users)
                {
                    try
                    {
                        lib.ReadPatronDetails(user);
                    }
                    catch (HorizonError he)
                    {
                        sb.AppendFormat("  LE: {0}/{1}\n", user.Name, he.Message);
                        continue;
                    }
                    catch (Exception e)
                    {

                        sb.AppendFormat("  IE: {0}/{1}\n", user.Name, e.Message);
                        continue;
                    }

                    sb.AppendFormat("{0} ({1} out)\n", user.Name, user.ItemsOut.Count);

                    foreach (Holding holding in user.ItemsOut)
                    {
                        TimeSpan ts = DateTime.Parse(holding.DueDate).Subtract(DateTime.Now.Date);
                        // If auto-renew is set, renew any books due today or already overdue
                        if( ts.Days <=2 )
                        {
                            if (ts.Days < 1 && Properties.Settings.Default.AutoRenewFlag)
                            {
                                lib.RenewHolding(user, holding);
                                status = "Auto-Renewed";
                            }
                            else if (ts.Days == 2)
                            {
                                status = "Due in two days";
                            }
                            else if (ts.Days == 1)
                            {
                                status = "Due tomorrow";
                            }
                            else if (ts.Days == 0)
                            {
                                status = "Due today";
                            }
                            else if (ts.Days < 0)
                            {
                                status = "** Overdue";
                            }

                            sb.AppendFormat("  {0}: {1} (due: {2})\n", status, holding.Title, holding.DueDate);
                        }
                    }

                    if (!string.IsNullOrEmpty(user.TotalFines))
                        sb.AppendFormat("  Fines: {0}\n", user.TotalFines);

                    if (user.Holds.Count > 0)
                        sb.AppendFormat("  Items held: {0} (first one: {1})\n", user.Holds.Count, user.Holds[0].Title);
                }
            }

            if (sb.Length > 0) appNotifyIcon.ShowBalloonTip(20, "Check it Out!", sb.ToString(), ToolTipIcon.Info);
        }

        /// <summary>
        /// Utility method to add a column to the DataGridView
        /// </summary>
        /// <param name="name">Display name for new column</param>
        /// <param name="dataProperty">Data binding for new column</param>
        private void AddHoldingsColumn(string name, string dataProperty)
        {
            DataGridViewTextBoxColumn c = new DataGridViewTextBoxColumn();
            c.Name = name;
            c.HeaderText = name;
            c.DataPropertyName = dataProperty;
            c.SortMode = DataGridViewColumnSortMode.Automatic;

            holdingsDataGridView.Columns.Add(c);
        }

        /// <summary>
        /// Invoked when the Search button is clicked or carriage return is pressed
        /// This is the AcceptButton for the form
        /// </summary>
        private void SearchButton_Click(object sender, EventArgs e)
        {
            PerformSearch();
        }

        /// <summary>
        /// Initializes the ServerRequest, then kicks off the search on the background thread
        /// </summary>
        private void PerformSearch()
        {
            if (searchBackgroundWorker.IsBusy) return;

            this.UseWaitCursor = true;
            SearchButton.Enabled = false;

            req.Aspect = "basic_search";
            req.SearchTerm = searchTermTextBox.Text;
            req.Index = searchTypeComboBox.SelectedValue.ToString();

            if (materialTypesComboBox.SelectedValue != null)
            {
                req.Limit = materialTypesComboBox.SelectedValue.ToString();
                req.Aspect = "subtab14";
            }
            else
                req.Limit = "";

            if (sortComboBox.SelectedValue != null)
            {
                req.Sort = sortComboBox.SelectedValue.ToString();
                req.Aspect = "subtab14";
            }
            else
                req.Sort = "";
            
            searchBackgroundWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Updates library-specific search values in combo boxes
        /// </summary>
        private void UpdateSearchFields()
        {
            materialTypesComboBox.DataSource = null;
            searchTypeComboBox.DataSource = null;
            sortComboBox.DataSource = null;

            if (lib != null)
            {
                //Data-bind material types
                materialTypesComboBox.DataSource = lib.SearchLimits;
                materialTypesComboBox.DisplayMember = "Description";
                materialTypesComboBox.ValueMember = "Key";

                searchTypeComboBox.DataSource = lib.SearchOptions;
                searchTypeComboBox.DisplayMember = "Label";
                searchTypeComboBox.ValueMember = "Key";

                sortComboBox.DataSource = lib.SortOptions;
                sortComboBox.DisplayMember = "Label";
                sortComboBox.ValueMember = "Key";

                if (lib.SearchOptions.Count > 0)
                {
                    // Must have one selected
                    searchTypeComboBox.SelectedIndex = 0;
                }
            }
        }

        /// <summary>
        /// Sets the PictureBox URL based on the selected holding
        /// </summary>
        private void holdingsDataGridView_SelectionChanged(object sender, EventArgs e)
        {
            if (holdingsDataGridView.SelectedRows.Count == 1)
            {
                try
                {
                    Holding h = (Holding)(holdingsDataGridView.SelectedRows[0].DataBoundItem);

                    if (string.IsNullOrEmpty(h.MediumImage))
                    {
                        holdingPictureBox.Image = Properties.Resources.ImageNotAvailable;
                    }
                    else
                    {
                        holdingPictureBox.Image = Properties.Resources.spinner;
                        holdingPictureBox.ImageLocation = h.MediumImage;
                    }
                }
                catch (Exception x)
                {
                    holdingPictureBox.Image = Properties.Resources.ImageError;
                    MessageBox.Show("Error: " + x);
                }
            }
        }

        /// <summary>
        /// Shows the Options form modally
        /// </summary>
        private void modifyLibrariesButton_Click(object sender, EventArgs e)
        {
            optForm.ShowDialog();
        }

        /// <summary>
        /// Updates search controls when library selection changes
        /// </summary>
        private void librariesComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            lib = (Library)(librariesComboBox.SelectedItem);
            UpdateSearchFields();
        }

        /// <summary>
        /// Saves object tree of libraries and patrons using binary serialization
        /// </summary>
        /// <param name="file">File to which save data</param>
        /// <param name="libs">Collection of libraries to save</param>
        public void Serialize(string file, List<Library> libs)
        {
            Stream stream = new FileStream(file, System.IO.FileMode.Create);
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, libs);

            stream.Close();
        }

        /// <summary>
        /// Recreates object tree of libraries and patrons from
        /// file containing binary serialization
        /// </summary>
        /// <param name="file">File from which to deserialize</param>
        /// <returns>List of libraries</returns>
        public List<Library> Deserialize(string file)
        {
            // Deserialize only if saved file exists
            if (File.Exists(file))
            {
                try
                {

                    Stream stream = new FileStream(file, System.IO.FileMode.Open);

                    IFormatter formatter = new BinaryFormatter();
                    List<Library> itemsDeserialized = (List<Library>)formatter.Deserialize(stream);

                    stream.Close();

                    return itemsDeserialized;
                }
                catch
                {
                    // Something is wrong.  Drop through and return a new file
                }
            }

            return new List<Library>();

        }

        /// <summary>
        /// Shows the form when notify icon is double-clicked
        /// </summary>
        private void appNotifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Show();
        }

        /// <summary>
        /// Shows options form based on notify icon menu command clicked
        /// </summary>
        private void showOptionsWindowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            optForm.Show();
        }

        /// <summary>
        /// Updates patron information based on notify icon menu command clicked
        /// </summary>
        private void updatePatronInformationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            UpdateAllPatrons();
        }

        /// <summary>
        /// Quits the application.  This sets the "exiting" flag so forms know
        /// now to cancel the Form_Closing event as they otherwise do.
        /// </summary>
        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            exiting = true;
            optForm.Exiting = true;
            Application.Exit();
        }

        /// <summary>
        /// Updates the UI when the form is shown
        /// </summary>
        private void MainForm_Shown(object sender, EventArgs e)
        {
            librariesComboBox.DataSource = null;
            librariesComboBox.DataSource = optForm.Libraries;
            librariesComboBox.DisplayMember = "Name";

            if (librariesComboBox.Items.Count > 0)
                librariesComboBox.SelectedIndex = 0;

            searchTermTextBox.Focus();
        }

        /// <summary>
        /// Updates patron information when timer ticks
        /// </summary>
        private void updatePatronsTimer_Tick(object sender, EventArgs e)
        {
            UpdateAllPatrons();
        }

        /// <summary>
        /// Shows the form based on clicking the notify icon menu command
        /// </summary>
        private void showSearchWindowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Show();
        }

        /// <summary>
        /// When Options form is closed, updates timer, libraries ComboBox, and saves all settings
        /// </summary>
        void optForm_VisibleChanged(object sender, EventArgs e)
        {
            // If it's just been hidden...
            if (optForm.Visible == false)
            {
                // Modify the timer appropriately
                updatePatronsTimer.Enabled = Properties.Settings.Default.AutomaticUpdatesFlag;
                updatePatronsTimer.Interval = (int)(Properties.Settings.Default.UpdateInterval * 60 * 60 * 1000);

                Serialize("Libraries.ser", optForm.Libraries);
                Properties.Settings.Default.Save();

                librariesComboBox.DataSource = null;
                librariesComboBox.DataSource = optForm.Libraries;
                librariesComboBox.DisplayMember = "Name";

                if (optForm.Libraries.Count > 0) librariesComboBox.SelectedIndex = 0;
            }
        }

        /// <summary>
        /// Saves settings and hides the form when selected.
        /// </summary>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            Properties.Settings.Default.Save();
            this.Hide();

            // If currently exiting, set icon to null (or it won't disappear)
            // Otherwise, cancel the event.  Otherwise the application exits.
            if (exiting)
            {
                appNotifyIcon.Icon = null;
            }
            else e.Cancel = true;
        }

        /// <summary>
        /// Updates ServerRequest object based on input and initiates search
        /// </summary>
        private void searchBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = lib.Search(req);
        }

        private void searchBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.UseWaitCursor = false;

            if (e.Error == null)
            {
                holdingsDataGridView.DataSource = e.Result;
            }
            else
            {
                if( e.Error is HorizonError )
                    MessageBox.Show(e.Error.Message);
                else
                    MessageBox.Show("Internal error\n\n" + e.Error);
            }

            SearchButton.Enabled = true;
        }
    }
}